package com.stable.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.google.common.util.concurrent.ListenableFuture;
import com.stable.constant.RedisConstant;
import com.stable.enums.RunCycleEnum;
import com.stable.enums.RunLogBizTypeEnum;
import com.stable.es.dao.base.EsProBarInfoDao;
import com.stable.es.dao.base.EsStockBaseInfoDao;
import com.stable.job.MyCallable;
import com.stable.spider.eastmoney.EastMoneySpider;
import com.stable.spider.ths.ThsBasicSpider;
import com.stable.spider.tushare.TushareSpider;
import com.stable.utils.*;
import com.stable.vo.IndustryAnalysis;
import com.stable.vo.bus.ProBarInfo;
import com.stable.vo.bus.StockBaseInfo;
import com.stable.vo.spi.req.ThsBasic;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Semaphore;

/**
 * 基本信息
 *
 * @author roy
 */
@Service
@Log4j2
public class StockBasicService {

    @Autowired
    private TushareSpider tushareSpider;
    @Autowired
    private EsStockBaseInfoDao esStockBaseInfoDao;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private EsProBarInfoDao esProBarInfoDao;
    // @Autowired
    // private DbStockBaseInfoDao dbStockBaseInfoDao;
    @Autowired
    private EastMoneySpider eastMoneySpider;
    @Autowired
    private ThsBasicSpider thsBasicSpider;

    private ConcurrentHashMap<String, String> CODE_NAME_MAP_LOCAL_HASH = new ConcurrentHashMap<String, String>();
    private List<StockBaseInfo> LOCAL_ALL_ONLINE_LIST = new CopyOnWriteArrayList<StockBaseInfo>();

    // 只初始化一次，历史数据包含已下市股票
    private boolean initedOneTime = true;

    public String getCodeName(String code) {
        String name = CODE_NAME_MAP_LOCAL_HASH.get(code);
        if (name == null) {
            loadAllLocalHash();
            name = CODE_NAME_MAP_LOCAL_HASH.get(code);
            if (name == null) {
                try {
                    if (initedOneTime) {
                        this.jobSynStockList().get();
                        initedOneTime = false;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                name = CODE_NAME_MAP_LOCAL_HASH.get(code);
                if (StringUtils.isBlank(name)) {
                    log.warn("已下市股票,code：{}", code);
                    CODE_NAME_MAP_LOCAL_HASH.put(code, "已下市");
                }
            }
        }
        return name;
    }

    private final Semaphore semap = new Semaphore(1);

    public ListenableFuture<Object> jobSynStockList() {
        try {
            semap.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return TasksWorker.getInstance().getService()
                .submit(new MyCallable(RunLogBizTypeEnum.STOCK_LIST, RunCycleEnum.WEEK) {
                    public Object mycall() {
                        try {
                            log.info("同步股票列表[started]");
                            JSONArray array = tushareSpider.getStockCodeList(); //返回所有A股列表
                            // System.err.println(array.toJSONString());
                            for (int i = 0; i < array.size(); i++) {
                                StockBaseInfo base = new StockBaseInfo(array.getJSONArray(i));
                                synBaseStockInfo(base);
                                log.info(base.getTs_code());
                            }
                            log.info("同步股票列表[end]");
                            return null;
                        } finally {
                            semap.release();
                        }
                    }
                });
    }

    public void synBaseStockInfo(StockBaseInfo base) {
        esStockBaseInfoDao.save(base);
        redisUtil.set(base.getCode(), base);
        // dbStockBaseInfoDao.saveOrUpdate(base);
        CODE_NAME_MAP_LOCAL_HASH.put(base.getCode(), base.getName());
        log.info("syn stock code list:{}", base);
    }

    public void loadAllLocalHash() {
        Iterator<StockBaseInfo> it = esStockBaseInfoDao.findAll().iterator();
        while (it.hasNext()) {
            StockBaseInfo e = it.next();
            CODE_NAME_MAP_LOCAL_HASH.put(e.getCode(), e.getName());
        }
    }

    public List<StockBaseInfo> getAllOnStatusList() {
        if (LOCAL_ALL_ONLINE_LIST.isEmpty()) {
            Iterator<StockBaseInfo> it = esStockBaseInfoDao.findAll().iterator();
            // List<StockBaseInfo> list = new LinkedList<StockBaseInfo>();
            while (it.hasNext()) {
                StockBaseInfo e = it.next();
                // list_status='L'
                if ("L".equals(e.getList_status())) {
                    // list.add(e);
                    LOCAL_ALL_ONLINE_LIST.add(e);
                }
            }
        }
        return LOCAL_ALL_ONLINE_LIST;
        // return dbStockBaseInfoDao.getListWithOnStauts();
    }


    private final Semaphore semapProBar = new Semaphore(1);

    public ListenableFuture<Object> jobSynStockProBar(String tsCode, boolean skipTo, String startDate, int insertCount) {
        try {
            semapProBar.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return TasksWorker.getInstance().getService()
                .submit(new MyCallable(RunLogBizTypeEnum.PRO_BAR_HISTORY, RunCycleEnum.DAY) {
                    public Object mycall() {
                        try {
                            log.info("股票通用行情历史数据列表[started]");
                            // 单个数据的插入
                            if (!StringUtils.isEmpty(tsCode) && !skipTo) {
                                StockBaseInfo base = new StockBaseInfo();
                                base.setCode(TushareSpider.removets(tsCode));
                                base.setTs_code(tsCode);
                                List<ProBarInfo> list = synProBarStockInfo(base, startDate, insertCount);
                                if (list.size() > 0) {
                                    esProBarInfoDao.saveAll(list);
                                }
                                log.warn("synProBarStockInfo：code：{}，插入ES 条数：size:{}，", tsCode, list.size());
                                //多个数据的插入，如果满足skipTo=true则意思为跳过指定tsCode，开始做更新操作,否则做全量的更新
                            } else {
                                boolean insertData = false;//插入数据的标识位
                                List<ProBarInfo> preInsertList = new ArrayList<>();//预存储需要放入ES的数据
                                if (StringUtils.isEmpty(tsCode)) {
                                    insertData = true;
                                }
                                JSONArray array = tushareSpider.getStockCodeList();
                                for (int i = 0; i < array.size(); i++) {
                                    StockBaseInfo base = new StockBaseInfo(array.getJSONArray(i));
                                    String infoTsCode = base.getTs_code();
                                    if (StringUtils.equals(tsCode, infoTsCode)) {
                                        insertData = true;
                                    }
                                    if (insertData) {
                                        List<ProBarInfo> list = synProBarStockInfo(base, startDate, insertCount);
                                        if (list.size() > 0) {
                                            preInsertList.addAll(list);
                                        }
                                    }
                                    // 批量插入，满足300条才插入1次,如果一次大批量插入则6000条插入一次
                                    int batchInsertCount = 300;
                                    if (insertCount > 50) batchInsertCount = 6000;
                                    if (preInsertList.size() > batchInsertCount || (i + 1 == array.size())) {
                                        esProBarInfoDao.saveAll(preInsertList);
                                        log.warn("synProBarStockInfo：循环列表插入ES 条数：size:{}，", preInsertList.size());
                                        preInsertList.clear();
                                    }
                                }
                            }
                            log.info("股票通用行情历史数据列表[end]");
                            return null;
                        } finally {
                            semapProBar.release();
                        }
                    }
                });
    }

    @Value("${python.file.market.probar}")
    private String pythonProbarFileName;


    public List<ProBarInfo> synProBarStockInfo(StockBaseInfo base, String startDate, int insertCount) {
        List<ProBarInfo> list = new ArrayList<>();
        String code = base.getCode();
        String ts_code = base.getTs_code();
        String start_date = "";
        if (StringUtils.isEmpty(startDate)) {
            start_date = DateUtil.getYYYYMMDD(DateUtil.addDate(new Date(), -770));
        } else {
            start_date = startDate;
        }
        String end_date = DateUtil.getTodayYYYYMMDD();

        String params = ts_code + " " + start_date + " " + end_date + " qfq D";
        List<String> lines = PythonCallUtil.callPythonScript(pythonProbarFileName, params);
        if (lines == null || lines.isEmpty() || lines.get(0).startsWith(PythonCallUtil.EXCEPT)) {
            log.warn("synProBarStockInfo：code：{}，未获取到数据 params：{}", ts_code, params);
            if (lines != null && !lines.isEmpty()) {
                log.error("Python 错误：code：{}，PythonCallUtil.EXCEPT：{}", ts_code, lines.get(0));
            }
            return list;
        }
        int index = 0;
        for (String line : lines) {
            ProBarInfo d = this.getTradeHistInfoDaliy(line);
            if (!Objects.isNull(d) && d.getMa250() != 0) {
                list.add(d);
                index++;
                if (index == insertCount) break;
            }
        }
        log.warn("synProBarStockInfo：code：{}，获取到数据条数：size:{}，", ts_code, list.size());
        return list;
    }


    private ProBarInfo getTradeHistInfoDaliy(String line) {
        if (StringUtils.isBlank(line)) {
            return null;
        }
        ProBarInfo d = new ProBarInfo();
        try {
            String str = line.trim().substring(1);
            String[] fv = str.split(",");
            if (StringUtils.isEmpty(fv[19]) || StringUtils.equals("nan", fv[19])) {  //没有ma250年线指标数据不要
                return d;
            }
            d.setCode(TushareSpider.removets(fv[0]));
            d.setTsCode(fv[0]);
            d.setTradeDate(Integer.valueOf(fv[1]));
            d.setOpen(Double.valueOf(fv[2]));
            d.setHigh(Double.valueOf(fv[3]));
            d.setLow(Double.valueOf(fv[4]));
            d.setClosed(Double.valueOf(fv[5]));
            d.setPreClose(Double.valueOf(fv[6]));
            d.setChange(Double.valueOf(fv[7]));
            d.setChangeRate(Double.valueOf(fv[8]));
            d.setVolume(Double.valueOf(fv[9]));
            d.setAmount(Double.valueOf(!StringUtils.isEmpty(fv[10]) || StringUtils.equals("nan", fv[10]) ? fv[10] : "0"));
            d.setMa5(Double.valueOf(!StringUtils.isEmpty(fv[11]) || StringUtils.equals("nan", fv[11]) ? fv[11] : "0"));
            d.setMa20(Double.valueOf(!StringUtils.isEmpty(fv[13]) || StringUtils.equals("nan", fv[13]) ? fv[13] : "0"));
            d.setMa30(Double.valueOf(!StringUtils.isEmpty(fv[15]) || StringUtils.equals("nan", fv[15]) ? fv[15] : "0"));
            d.setMa60(Double.valueOf(!StringUtils.isEmpty(fv[17]) || StringUtils.equals("nan", fv[17]) ? fv[17] : "0"));
            d.setMa250(Double.valueOf(!StringUtils.isEmpty(fv[19]) || StringUtils.equals("nan", fv[19]) ? fv[19] : "0"));
            d.setId();
        } catch (Exception e) {
            ErrorLogFileUitl.writeError(e, "日K数据错误", "原始数据", line);
            log.error(e.getMessage(), e);
//            throw new RuntimeException(e);
        }
        return d;
    }


    public ListenableFuture<Object> jobSynCodeList() {
        try {
            semapProBar.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return TasksWorker.getInstance().getService()
                .submit(new MyCallable(RunLogBizTypeEnum.STOCK_LIST, RunCycleEnum.WEEK) {
                    public Object mycall() {
                        try {
                            log.info("股票列表同步[started]");
                            JSONArray array = tushareSpider.getStockCodeList();
                            for (int i = 0; i < array.size(); i++) {
                                StockBaseInfo base = new StockBaseInfo(array.getJSONArray(i));
                                // 设置概念
                                String conception = eastMoneySpider.getCoreConception(base.getCode());
                                base.setConcept_name(conception);
                                // 设置行业排名
                                IndustryAnalysis industryAnalysis = eastMoneySpider.getIndustryAnalysis(base.getCode());
                                base.setCzxbj_pm(industryAnalysis.getCzxbj_pm());
                                base.setCzxbj_jbmgsyzzlfh(industryAnalysis.getCzxbj_jbmgsyzzlfh());
                                base.setGzbj_pm(industryAnalysis.getGzbj_pm());
                                base.setGzbj_peg(industryAnalysis.getGzbj_peg());
                                base.setDbfxbj_pm(industryAnalysis.getDbfxbj_pm());
                                base.setDbfxbj_roepj(industryAnalysis.getDbfxbj_roepj());
                                base.setGsgmzsz_pm(industryAnalysis.getGsgmzsz_pm());
                                base.setGsgmzsz_zsz(industryAnalysis.getGsgmzsz_zsz());

                                //用已有的公司亮点数据填充
                                String stockBasicStr = (String) redisUtil.hGet(RedisConstant.stock_basic, base.getCode());
                                if (StringUtils.isNotBlank(stockBasicStr) && !stockBasicStr.equals("[]")) {
                                    StockBaseInfo thsBasicInfo = JSON.parseObject(stockBasicStr, StockBaseInfo.class);
                                    base.setGsld(thsBasicInfo.getGsld());// 公司亮点
                                    base.setSshy(thsBasicInfo.getSshy());// 所属申万行业
                                    base.setZxjj(thsBasicInfo.getZxjj());// 最新解禁
                                    base.setZybl(thsBasicInfo.getZybl());// 已经质押股份占A股总股本比
                                }
                                redisUtil.hPut(RedisConstant.stock_basic, base.getCode(), JSON.toJSONString(base));

                                log.info(JSON.toJSONString(base));
                            }
                            log.info("股票列表同步[end]");
                            return null;
                        } finally {
                            semapProBar.release();
                        }
                    }
                });
    }

    //根据同花顺更新公司亮点及质押等数据信息
    public ListenableFuture<Object> jobSynCodeTopic() {
        try {
            semapProBar.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return TasksWorker.getInstance().getService()
                .submit(new MyCallable(RunLogBizTypeEnum.THS_TOPIC_BASIC, RunCycleEnum.WEEK) {
                    public Object mycall() {
                        try {
                            log.info("根据同花顺更新公司亮点及质押等数据信息[started]");
                            Map<Object, Object> stockBasicMap = redisUtil.hGetAll(RedisConstant.stock_basic);
                            stockBasicMap.values().stream().forEach(info -> {
                                StockBaseInfo base = JSON.parseObject((String) info, StockBaseInfo.class);
                                ThsBasic thsBasicInfo = thsBasicSpider.getTHSBasicInfo(base.getCode());
                                base.setGsld(thsBasicInfo.getGsld());// 公司亮点
                                base.setSshy(thsBasicInfo.getSshy());// 所属申万行业
                                base.setZxjj(thsBasicInfo.getZxjj());// 最新解禁
                                base.setZybl(thsBasicInfo.getZybl());// 已经质押股份占A股总股本比

                                redisUtil.hPut(RedisConstant.stock_basic, base.getCode(), JSON.toJSONString(base));
                                log.info(JSON.toJSONString(base));
                            });
                            log.info("根据同花顺更新公司亮点及质押等数据信息[end]");
                            return null;
                        } finally {
                            semapProBar.release();
                        }
                    }
                });
    }
}
